<?
// Copyright (C) 2004 E.F. de Moor
// This source is distributed under the GPL license.
// See the accompanying readme.txt and gpl.txt for more details

// Object oriented implementation of database access
// Current implementation uses MySQL.
// Future implementations might dirive from this class to make connections to other db types.
//
// Each table is required to have a column "id", that is unique, not null and autoincrement.

class db_if{
  // Private properties
  var $db_name;
  var $db_userid;
  var $db_passwd;
  var $db_handle;
  var $sqli;

  // Constructor for the db object
  function db_if($db_name='', $userid="www", $passwd="", $host='localhost') {
    $this->db_name = $db_name;
    $this->db_userid = $userid;
    $this->db_passwd = $passwd;
    $this->db_host = $host;
    $this->inserted_successfully = "inserted successfully...";
    $this->updated_successfully  = "updated successfully...";
    $this->deleted_successfully  = "deleted successfully...";
    $this->sqli = new mysqli($host, $userid, $passwd, $db_name);
    if ($this->sqli->connect_error)
      echo ("<p class=\"error\">Could not connect to database $this->db_name.</p>");
  }

  // Specify a (different) userid and password for the database
  function set_userid($userid,$passwd,$host='localhost') {
    if($host != $this->db_host) {
      $this->sqli->close();
      $this->sqli = new mysqli($this->db_host, $this->db_userid, $this->db_passwd, $this->db_name);
    }
    else if($userid) {
      $this->db_userid = $userid;
      $this->db_passwd = $passwd;
      $this->db_host = $host;
      if(!$this->sqli->change_user($this->db_userid, $this->db_passwd, $this->db_name)) {
        $this->db_name = "";
        echo ("<p class=\"error\">Could change user to $this->db_userid.</p>");
      }
    }
  }

  // select a database
  function select_db($db_name='') {
    if($db_name)
      $this->db_name = $db_name;
    if($this->db_name) {
      if(!$this->sqli->select_db($this->db_name)) {
        $this->db_name = "";
        echo ("<p class=\"error\">Could not find database $this->db_name.</p>");
      }
    }
  }

  // Set the user feedback messages
  function set_messages($inserted_successfully, $updated_successfully, $deleted_successfully) {
    $this->inserted_successfully = $inserted_successfully;
    $this->updated_successfully  = $updated_successfully;
    $this->deleted_successfully  = $deleted_successfully;
  }

  // Get an overview of available tables
  function get_tables($db_name='') {
    if(!$db_name)
      $db_name = $this->db_name;
    $sql = "SHOW TABLES FROM $db_name";
    if ($result = $this->sqli->query($sql)) {
      $result->data_seek(0);
      while ($record = $result->fetch_assoc()) {
        $tables[] = reset($record);
      }
      $result->close();
      $this->select_db();  // Restore the current database, get_tables seems to select the requested one
    }
    return($tables);
  }

  // Get an overview of available fields
  function get_fields($db_table, $db_name='') {
    if (!$db_name) $db_name=$this->db_name;
    $sql = "select * from $db_table limit 1";
    if($result = $this->sqli->query($sql)) {
      for ($i=0; $i<$result->field_count; $i++){
        $field_info = $result->fetch_field_direct($i);
        $fields[$field_info->name]['name']   = $field_info->name;
        $fields[$field_info->name]['typeid'] = $field_info->type;
        $fields[$field_info->name]['dbtype'] = $this->get_type_str($field_info->type);
        $fields[$field_info->name]['len']    = $field_info->length;
        $fields[$field_info->name]['flag']   = $this->get_flag_str($field_info->type, $field_info->flags);
        $fields[$field_info->name]['char']   = $this->get_type_char($fields[$field_info->name]['dbtype']);
      }
      return($fields);
    }
    return false;
  }


  // List all values from enum or set columns
  function list_values($tableName,$fieldName,$db_name='') {
    if(!$db_name)
      $db_name = $this->db_name;
    if($result = $this->sqli->query("DESCRIBE $dbname.$tableName")) {
      while ($record = $result->fetch_assoc()) {
        if ($record['Field']==$fieldName) {
          preg_match('/^([^ (]+)(\((.+)\))?([ ](.+))?$/',$record['Type'],$fieldTypeSplit);
          $fieldType = $fieldTypeSplit[1];
          $fieldOpts = $fieldTypeSplit[3];
          //split type up into array
          if ( $fieldType=='enum' || $fieldType=='set'){
            $fieldOptions = explode("','",substr($fieldOpts,1,-1));
            return $fieldOptions;
          }
        }
      }
    }
    return false;
  }

  // Do a single query from the current db and return the result as db_result
  function execute($sql, $values=array(), $types="") {
    $db_result = new db_result();
    $db_result->db_name = $this->db_name;
    if(count($values)) {  // If there are parametes, prepare a query. Otherwise just do a direct query
      if(!$types) {
        foreach($values as $value) {
          switch (gettype($value)) {
            case "boolean":
            case "integer":
              $types .= "i";
              break;
            case "double":
              $types .= "d";
              break;
            default:
              $types .= "s";
              break;
          }
        }
      }
      $db_result->sql = $sql;
      $db_result->types = $types;
      $db_result->values = implode(',', $values);
      $stmt = $this->sqli->prepare($sql);
      if($stmt) {
        $parms = array();
        $parms[] = &$types;
        foreach($values as $key=>$value)
          $parms[] = &$values[$key];
        if(call_user_func_array(array($stmt,"bind_param"), $parms)) {
          if($stmt->execute()) {
            $stmt->store_result();
            $db_result->errno = $this->sqli->errno;
            $db_result->error = $this->sqli->error;
            $db_result->affected_rows = $this->sqli->affected_rows;
            $db_result->last_id = $this->sqli->insert_id;
            $meta = $stmt->result_metadata();
            if($meta && $meta->field_count) {
              while ($column = $meta->fetch_field()) {
                $columns[] = &$results[str_replace(' ', '_', $column->name)];
              }        
              call_user_func_array(array($stmt, 'bind_result'), $columns);
              while($stmt->fetch()) {
                foreach ($results as $k => $v) {
                  $record[$k] = $v;
                }
                $db_result->rows[] = $record;
                $db_result->num_rows++;
              }
            }
          }
          else {
            if ($this->sqli->errno) $db_result->errno = $this->sqli->errno;
            else $db_result->errno = -1;
            if ($this->sqli->error) $db_result->error = $this->sqli->error;
            else $db_result->error = "MySQL Execute failed";
          }
        }
        else {
          if ($this->sqli->errno) $db_result->errno = $this->sqli->errno;
          else $db_result->errno = -2;
          if ($this->sqli->error) $db_result->error = $this->sqli->error;
          else $db_result->error = "MySQL Bind failed";
        }
      }
      else {
        if ($this->sqli->errno) $db_result->errno = $this->sqli->errno;
        else $db_result->errno = -3;
        if ($this->sqli->error) $db_result->error = $this->sqli->error;
        else $db_result->error = "MySQL Prepare failed";
      }
    } else {
      $db_result->sql = $sql;
      $result = $this->sqli->query($db_result->sql);
      if($result) {
        // succesfull
        $db_result->errno = 0;
        $db_result->error = "";
        $db_result->affected_rows = $this->sqli->affected_rows;
        $db_result->last_id = $this->sqli->insert_id;
        if ($result !== TRUE) {
          // it was a select, read values
          $result->data_seek(0);
          while ($record = $result->fetch_assoc()) {
            $db_result->rows[] = $record;
            $db_result->num_rows++;
          }
          $result->close();
        }
      } else {
        $db_result->errno = $this->sqli->errno;
        $db_result->error = $this->sqli->error;
      }
    }
    // debug
    //print_r($db_result);
    //$trace = debug_backtrace();
    //foreach($trace as $trace_info)
    //  echo 'called from:'.$trace_info['file'].': '.$trace_info['line']."<br>\n";
    return($db_result);
  }

  // DEPRECATED, WILL BE REMOVED IN CMS V3.0
  // Do a query from available db and return the result as db_result
  // Note: A query may contain ';' to separate multiple subqueries. The result
  // of the previous query can be used by specifying a '@' in the query.
  function query($sql) {
    global $cms;
    $cms->deprecated('Function db->query is deprecated. Use db->execute instead.');

    $db_result = new db_result();
    $db_result->db_name = $this->db_name;
    $db_result->sql = $sql;
    $result = $this->sqli->query($db_result->sql);
    if($result) {
      // succesfull
      $db_result->errno = 0;
      $db_result->error = "";
      $db_result->affected_rows = $this->sqli->affected_rows;
      $db_result->last_id = $this->sqli->insert_id;
      if ($result !== TRUE) {
        // it was a select, read values
        $result->data_seek(0);
        while ($record = $result->fetch_assoc()) {
          $db_result->rows[] = $record;
          $db_result->num_rows++;
        }
        $result->close();
      }
    } else {
      $db_result->errno = $this->sqli->errno;
      $db_result->error = $this->sqli->error;
    }
    return($db_result);
  }

  // Read a single value from the database.
  function read_value($sql, $values=array(), $types="") {
    $result = $this->execute($sql, $values, $types);
    if($result)
      return($result->get_value());
    else
      return(false);
  }

  // Read a single value from the database.
  // table: the database table to read from
  // field: the value of the field to return
  // id: the id of the record to read
  function read_value_by_id($table, $field, $id) {
    $sql = "select $field from $table where id=?";
    $result = $this->execute($sql, array($id), 'i');
    if($result)
      return($result->get_value());
    else
      return(false);
  }


  // DEPRECATED, WILL BE REMOVED IN CMS V3.0
  // Query a single value from the database.
  function query_value($sql) {
    global $cms;
    $cms->deprecated('Function db->query_value is deprecated. Use db->read_value instead.');
    $result = $this->query($sql);
    if($result)
      return($result->get_value());
    else
      return(false);
  }

  // Read an array of value from the database.
  // Note: the query should return 2 values, first becomes the key of the array
  // Optionally a sort column can be specified for natsort
  function read_value_array($sql, $values=array(), $types="", $sort_column='', $reverse=0) {
    $result = $this->execute($sql, $values, $types);
    if($result)
      return $result->get_value_array($sort_column, $reverse);
    else
      return false;
  }

  // DEPRECATED, WILL BE REMOVED IN CMS V3.0
  // Query an array of value from the database.
  // Note: the query should return 2 values, first becomes the key of the array
  // Optionally a sort column can be specified for natsort
  function query_value_array($sql, $sort_column='', $reverse=0) {
    global $cms;
    $cms->deprecated('Function db->query_value_array is deprecated Use db->read_value_array instead.');
    $result = $this->query($sql);
    if($result)
      return($result->get_value_array($sort_column, $reverse));
    else
      return(false);
  }

  // Query an array of values
  // Note: the query should return 1 value
  // Optionally a sort column can be specified for natsort
  function read_values($sql, $values=array(), $types="", $sort_column='', $reverse=0) {
    $result = $this->execute($sql, $values, $types);
    if($result)
      return($result->get_values($sort_column, $reverse));
    else
      return(false);
  }

  // DEPRECATED, WILL BE REMOVED IN CMS V3.0
  // Query an array of values
  // Note: the query should return 1 values
  // Optionally a sort column can be specified for natsort
  function query_values($sql, $sort_column='', $reverse=0) {
    global $cms;
    $cms->deprecated('Function db->query_values is deprecated. Use db->read_values instead.');
    $result = $this->query($sql);
    if($result)
      return($result->get_values($sort_column, $reverse));
    else
      return(false);
  }

  // Delete a record from the database, with the indicated id
  //    id          = id of the item to be deleted (value in the id column)
  //    item_type   = type of item to be deleted, used for textual response
  function delete($id, $table, $item_type='') {
    $sql = "DELETE FROM $table WHERE id=?";
    $result = $this->execute($sql,array($id), "i");
    if ($result->errno)
      $result->message = "SQL Error - " . mysql_error($this->db_handle);
    else
      $result->message = "$item $this->deleted_successfully!";
    return $result;
  }

  // Retrieve a tuple from the database, an array with values is returned.
  function get_tuple_array($table, $id) {
    $sql = "SELECT * FROM $table WHERE id=?";
    $result = $this->execute($sql,array($id),"i");
    if($result && $result->num_rows) {
      return($result->get_firstrow());
    }
    else
      return(false);
  }


  // Perform a query on the database, fill the given array with values
  function read_array(&$var_array, $sql, $values=array(), $types="", $verbose=1, $merge=0) {
    $var_array = NULL; // AvD on 3th december 2004: Prepare for failing queries...
    $result = $this->execute($sql, $values, $types);
    if ($result->errno && verbose) {
      printf("<b class=error>MySql Error %s:</b> %s<br>\n", $result->errno, $result->error);
      printf("<b class=error>ERROR</b> in %s: %s<br>\n", __FILE__, $result->sql);
    } else {
      $row = $result->get_firstrow();
      while ($row && list($var, $val) = each($row)) {
        if (!$merge || $val)
          $var_array[$var] = $val;    // Stick to original if $field = NULL...
      }
      // return the first value of the array.
      // This makes the function even easier to use as a kind of boolean function...
      if(is_array($var_array))
        return(reset($var_array));
      else
        return false;
    }
  }

  // DEPRECATED, WILL BE REMOVED IN CMS V3.0
  // Perform a query on the database, fill the given array with values
  function fill_array(&$var_array, $sql, $verbose=1, $merge=0) {
    global $cms;
    $cms->deprecated('Function db->fill_array is deprecated. Use db->read_array instead.');
    $var_array = NULL; // AvD on 3th december 2004: Prepare for failing queries...
    $result = $this->query($sql);
    if ($result->errno && verbose) {
      printf("<b class=error>MySql Error %s:</b> %s<br>\n", $result->errno, $result->error);
      printf("<b class=error>ERROR</b> in %s: %s<br>\n", __FILE__, $result->sql);
    } else {
      $row = $result->get_firstrow();
      while ($row && list($var, $val) = each($row)) {
        if (!$merge || $val)
          $var_array[$var] = $val;    // Stick to original if $field = NULL...
      }
      // return the first value of the array.
      // This makes the function even easier to use as a kind of boolean function...
      if(is_array($var_array))
        return(reset($var_array));
      else
        return false;
    }
  }

  // Update or insert (if not already found) a tuple in a DB table.
  // The function automatically select customer db if available, otherwise reseller db
  //    var_array   = input array with variables to be written, GLOBALS if not given
  //    table       = tablename
  //    item        = human descriptor
  //    merge       = when 1, do not overwrite with empty values
  //                  This allows to give only new values, and leave out the existing fields.
  // The function returns a string with a result description (or error description)
  function update_tuple_array ($var_array, $table, $item_type, $merge=0, $debug=0) {
    $field_processed = 0;
    $fields_sql = '';
    $values_sql = '';
    $update_sql = '';
    $insert_types = '';
    $update_types = '';
    $insert_array= array();
    $update_array = array();
    $fields = $this->get_fields($table);
    $i = 0;
    foreach($fields as $field) {
      $field_name = $field['name'];
      if ($field['dbtype'] != 'timestamp' ) { // TIMESTAMP
        if (!$merge || isset($var_array[$field_name])) {
          // allow NULL for DATE and DATETIME types
          if (($field['dbtype']=='date' || $field['dbtype']=='datetime') && $var_array[$field_name]=='')
            $value = NULL;
          else {
            $value = $var_array[$field_name];
            if($value==NULL)
              if($field['dbtype'] == 'string' || $field['dbtype'] == 'blob')
                $value='';
              else
                $value=0;
          }
          if ($i) {
            $fields_sql .= ", ";
            $values_sql .= ", ";
            $update_sql .= ", ";
          }
          $fields_sql .= $field_name;
          $values_sql .= '?';
          $update_sql .= "$field_name=?";
          $insert_types .= $field['char'];
          $update_types .= $field['char'];
          $insert_array[] = $value;
          $update_array[] = $value;
          $i++;
        }
      }
    }
    $sql = "INSERT INTO $table ($fields_sql) VALUES ($values_sql) ON DUPLICATE KEY UPDATE $update_sql";
    if($debug)
      echo "<!-- update SQL = $sql -->\n";
    $result = $this->execute($sql, array_merge($insert_array,$update_array), $insert_types.$update_types);
    $result->last_id = $var_array["id"];
    if(!$result->last_id) {
      $result->last_id = $this->sqli->insert_id;
      $result->message = "$item $this->inserted_successfully";
    }
    else {
      $result->message = "$item $this->updated_successfully";
    }
    if ($result->errno) {
      $result->message = "SQL Error - $result->error";
    }
    return $result;
  }

  // Delete a record from the database, indicated by the array
  function delete_tuple_array (&$var_array, $table, $item_type='') {
    return $this->delete($var_array['id'], $table, $item_type);
  }

  // Update or insert (if not already found) an object in a DB table.
  //    object      = object to be stored
  //    table       = tablename
  //    item        = human descriptor
  //    merge       = when 1, do not overwrite with empty values
  // The function returns a string with a result description (or error description)
  function update_object(&$object, $table, $item_type, $merge=0) {
    $result = $this->update_tuple_array (get_object_vars($object), $table, $item_type, $merge);
    if(!$object->id)
      $object->id = $result->last_id;
    return($result);
  }

  function delete_object ($object, $table, $item_type) {
    return $this->delete_tuple_array(get_object_vars($object), $table, $item_type);
  }

  // Return an array of objects of given class, that is the result of the sql query
  // On error, an empty array is returned
  function make_objects($class, $sql, $values=array(), $types='', $sort_column='', $reverse=0) {
    $item_list = $this->execute($sql, $values, $types);
    if($item_list->error)
      return(false);
    $objects = array();
    if($item_list->num_rows && $sort_column)
      $item_list->natsort($sort_column, $reverse);
    while ($item_list && $item_list->num_rows && $item = $item_list->get_nextrow()) {
      $object = new $class;
      while (list($key,$value)=each($item)) {
        if (!is_numeric($key)) {
          $object->$key = $value;
        }
      }
      $objects[] = $object;
    }
    return($objects);
  }

  // DEPRECATED, WILL BE REMOVED IN CMS V3.0
  // Return an array of objects of given class, that is the result of the sql query
  // On error, an empty array is returned 
  function get_objects($class, $sql, $sort_column='', $reverse=0) {
    global $cms;
    $cms->deprecated('Function db->get_objects is deprecated. Use db->make_objects instead.');
    $item_list = $this->query($sql);
    if($item_list->error)
      return(false);
    $objects = array();
    if($item_list->num_rows && $sort_column)
      $item_list->natsort($sort_column, $reverse);
    while ($item_list && $item_list->num_rows && $item = $item_list->get_nextrow()) {
      $object = new $class;
      while (list($key,$value)=each($item)) {
        if (!is_numeric($key)) {
          $object->$key = $value;
        }
      }
      $objects[] = $object;
    }
    return($objects);
  }

  // Return an object of given class, identified by table and id
  function make_object_by_id ($class, $table, $id) {
    $sql  = "SELECT * FROM $table WHERE id=?";
    $item_list = $this->execute($sql,array($id),"s");
    if($item_list->error)
      return(false);
    $item = $item_list->get_firstrow();
    $object = new $class;
    if(is_array($item)) {
      while (list($key,$value)=each($item)) {
        if (!is_numeric($key)) {
          $object->$key = $value;
        }
      }
    }
    return ($object);
  }

  // DEPRECATED, WILL BE REMOVED IN CMS V3.0
  // Return an object of given class, identified by table and id
  function get_object_by_id ($class, $table, $id) {
    global $cms;
    $cms->deprecated('Function db->get_object_by_id is deprecated. Use db->make_object_by_id instead.');
    return $this->make_object_by_id($class, $table, $id);
  }

  // Fill the given object by the result of the sql query. Return false if this failed
  function read_object(&$obj ,$sql, $values=array(), $types='') {
    $item_list = $this->execute($sql, $values, $types);
    if ($item_list && $item_list->num_rows && $item = $item_list->get_firstrow()) {
      while (list($key,$value)=each($item)) {
        if (!is_numeric($key)) {
          $obj->$key = $value;
        }
      }
      return(true);
    }
    return(false);
  }

  // DEPRECATED, WILL BE REMOVED IN CMS V3.0
  // Fill the given object by the result of the sql query. Return false if this failed
  function fill_object(&$obj ,$sql) {
    global $cms;
    $cms->deprecated('Function db->fill_object is deprecated. Use db->read_object instead.');
    $item_list = $this->query($sql);
    if ($item_list && $item_list->num_rows && $item = $item_list->get_firstrow()) {
      while (list($key,$value)=each($item)) {
        if (!is_numeric($key)) {
          $obj->$key = $value;
        }
      }
      return(true);
    }
    return(false);
  }

  // Fill the given object identified by table and id. Return false if this failed
  function read_object_by_id (&$obj, $table, $id) {
    $sql  = "SELECT * FROM $table WHERE id=?";
    return $this->read_object($obj, $sql, array($id), "i");
  }


  function fill_object_by_id (&$obj, $table, $id) {
    global $cms;
    $cms->deprecated('Function db->fill_object_by_id is deprecated. Use db->read_object_by_id instead.');
    return $this->read_object_by_id ($obj, $table, $id);
  }


  function get_type_str($type_id) {
    switch($type_id) {
      case   0: return 'real';      // DECIMAL
      case   1: return 'int';       // TINYINT
      case   2: return 'int';       // SMALLINT
      case   3: return 'int';       // INT  // Note: max value = 2147483647
      case   4: return 'real';      // FLOAT
      case   5: return 'real';      // DOUBLE
      case   6: return 'int';       // NULL
      case   7: return 'timestamp'; // TIMESTAMP
      case   8: return 'int';       // BIGINT // Store as int, Note: max value = 2147483647
      case   9: return 'int';       // MEDIUMINT
      case  10: return 'date';      // DATE
      case  11: return 'time';      // TIME
      case  12: return 'datetime';  // DATETIME
      case  13: return 'year';      // YEAR
      case  14: return 'date';      // NEWDATE
      case  16: return 'int';       // BIT
      case 246: return 'real';      // DECIMAL
      case 247: return 'string';    // ENUM
      case 248: return 'string';    // SET
      case 249: return 'blob';      // TINY_BLOB
      case 250: return 'blob';      // MEDIUM_BLOB
      case 251: return 'blob';      // LONG_BLOB
      case 252: return 'blob';      // BLOB
      case 253: return 'string';    // VARCHAR
      case 254: return 'string';    // CHAR
      case 255: return 'string';    // GEOMETRY
      default:  return 'string';
    }
  }

  function get_flag_str($type, $flags) {
    $res = array();
    if ($type == 247)  $res[] = 'enum';           /* ENUM */
    if ($flags &    1) $res[] = 'not_null';       /* NOT NULL */
    if ($flags &    2) $res[] = 'primary_key';    /* primary key */
    if ($flags &    4) $res[] = 'unique_key';     /* unique key */
    if ($flags &    8) $res[] = 'multiple_key';   /* multiple key */
    if ($flags &   16) $res[] = 'blob';           /* blob */
    if ($flags &   32) $res[] = 'unsigned';       /* unsigned */
    if ($flags &   64) $res[] = 'zerofill';       /* zerofill */
    if ($flags &  128) $res[] = 'binary';         /* binary   */
    if ($flags &  256) $res[] = 'enum';           /* enum */
    if ($flags &  512) $res[] = 'auto_increment'; /* autoincrement field */
    if ($flags & 1024) $res[] = 'timestamp';      /* timestamp */
    return implode(" ", $res);
  }

  function get_type_char ($type) {
    switch($type) {
      case 'int':       return 'i';
      case 'time':      return 's';
      case 'real':      return 's';
      case 'timestamp': return 's';
      case 'date':      return 's';
      case 'time':      return 's';
      case 'year':      return 'i';
      case 'blob':      return 's';
      case 'string':    return 's';
      default:          return 's';
    }
  }

}


// Object oriented implementation of a result of a SQL query

// Class implements a result object of an SQL query.
// This should be independent of the underlying database,
// although it is only tested with MySQL
class db_result
{
  // Public properties
  var $num_rows;        // amount of fields that was returned from mysql
  var $rows;            // array with actual data returned from the query
  var $index;           // read-index within the array
  var $errno;           // Mysql error if something went wrong. 0 if all is ok
  var $error;           // Textual description of error

  // Constructor: empty all relevant fields
  function db_result() {
      $this->num_rows   = 0;
      $this->index      = 0;
      $this->errno      = 0;
      $this->error      = "";
  }

  // Get a (the first available) value from an SQL query.
  // Most logical use is with a query that only results in a single value
  function get_value() {
    if(is_array($this->rows) && is_array($this->rows[0]))
      return(reset($this->rows[0]));
    else
      return(false);
  }

  // Get values.
  // Note: the query should return 1 value
  // Optionally a sort column can be specified for natsort
  function get_values($sort_column='', $reverse=0) {
    if(is_array($this->rows)) {
      if($sort_column)
        $this->natsort($sort_column, $reverse);
      $array = array();
      foreach($this->rows as $record) {
        $array[] = reset($record);
      }
      return($array);
    }
    else
      return(false);
  }

  // Get value array from an SQL query.
  // Note: the query should return 2 values, first becomes the key of the array
  // Optionally a sort column can be specified for natsort
  function get_value_array($sort_column='', $reverse=0) {
    if(is_array($this->rows)) {
      if($sort_column)
        $this->natsort($sort_column, $reverse);
      $array = array();
      foreach($this->rows as $record) {
        // Take the first value if there are equal keys
        if(!$array[reset($record)])
          $array[reset($record)] = next($record);
      }
      return($array);
    }
    else
      return(false);
  }

  // Set the internal pointer to the first row
  function reset() {
    $this->index = 0;
  }

  // Put back one row (get it again with the next "get_next_row())
  function put_back() {
    $this->index--;
  }

  // Get the first row that resulted from the SQL query. 
  // The internal pointer is set to the first row
  // The row is returned as an array of values
  function get_firstrow() {
    $this->reset();
    return($this->get_nextrow());
  }

  // Get the next row that resulted from the SQL query. 
  // The row is returned as an array of values
  // The internal pointer is set to the next row
  function get_nextrow() {
    if(is_array($this->rows) && is_array($this->rows[$this->index])) {
      return($this->rows[$this->index++]);
    }
    else
      return(false);
  }

  // Get all rows as a multi dimensional array
  function get_allrows() {
    if(is_array($this->rows))
      return($this->rows);
    else
      return(false);
  }

  // Naturally sort the result, with the given column name.
  function natsort($column_name, $reverse=0) {
    // PHP doesn't allow to give custom parameters to the compare function.
    // Unfortunately it is called as class function, so we have to use global variables....
    global $db_if_row_sort_column_name;
    $db_if_row_sort_column_name = $column_name;
    if($column_name && is_array($this->rows)) {
      usort($this->rows, array("db_result", "row_cmp"));
      if($reverse)
        $this->rows = array_reverse($this->rows);
    }
  }

  // Function to compare two rows
  function row_cmp($a, $b) {
    global $db_if_row_sort_column_name;
    return strnatcasecmp($a[$db_if_row_sort_column_name], $b[$db_if_row_sort_column_name]);
  }
}

?>
